#include "core/Core.h"

extern std::function<void(word_t*)> dpic_set_gpr_ptr_handler;
extern std::function<void(word_t*)> dpic_set_csr_ptr_handler;
extern std::function<void(Trap, word_t)> dpic_take_trap_handler;

Core::Core() {
    ctx = new VerilatedContext;
    ctx->debug(0);
    ctx->randReset(2);
    top = new Vtop { ctx };
    connect_wire(master_ptr, top);
    this->master_ref = new axi4_ref<32,64,4> { master_ptr };

    dpic_set_gpr_ptr_handler = std::bind(&GPR::set_ptr, &gpr, std::placeholders::_1);
    dpic_set_csr_ptr_handler = std::bind(&CSR::set_ptr, &csr, std::placeholders::_1);
    dpic_take_trap_handler = std::bind(&Core::take_trap, this, std::placeholders::_1, std::placeholders::_2);

    pc = RESET_VECTOR;
}

Core::~Core() {
    top->final();
    IFDEF(CONFIG_VTRACE, if (vtrace != nullptr) delete vtrace);
    IFDEF(CONFIG_ITRACE, if (itrace != nullptr) delete itrace);
    IFDEF(CONFIG_DIFFTEST, if (ref_ctx != nullptr) delete ref_ctx);
    IFDEF(CONFIG_DIFFTEST, if (dut_ctx != nullptr) delete dut_ctx);
    if (top != nullptr) delete top;
    if (ctx != nullptr) delete ctx;
}

void Core::reset(int n) {
	top->reset = 1;
	for (int i = 0; i < n; ++i) {
	    one_cycle();
	}
	top->reset = 0;
    gpr.reset();
    csr.reset();
}

void Core::posedge() {
    top->clock = 1;
    joint.update_input(*master_ref);  // master -> joint
    top->eval();
    xbar->beat(joint_ref);            // joint <-> xbar
    joint.update_output(*master_ref); // master <- joint
	top->eval();

    IFDEF(CONFIG_VTRACE, vtrace->dump());
}

void Core::negedge() {
    top->clock = 0;
    top->eval();

    IFDEF(CONFIG_VTRACE, vtrace->dump());
}

void Core::one_cycle() {
    negedge();
	posedge();
    cycle_count++;
}

uint64_t Core::exec_one_inst(Commit& commit) {
    do { one_cycle(); } while (!top->commit_valid);
    commit.valid = top->commit_valid;
    commit.pc = top->commit_pc;
    commit.inst = top->commit_inst;
#ifdef CONFIG_DIFFTEST
    commit.skip_ref = top->commit_skip_ref;
    if (commit.skip_ref) {
        difftest_skip_ref();
    }
#endif
    while (!top->debug_wb_valid) { one_cycle(); }
    inst_count++;
	return top->debug_wb_pc;
}

void Core::step(bool msip, bool mtip) {
    static Commit commit;
    uint64_t prev_pc = this->pc;
#ifdef CONFIG_WATCHPOINT
    if (watchpoint.has(prev_pc) && !stop_flag) {
        stop_flag = true;
        return;
    }
    stop_flag = false;
#endif
    top->msi = msip;
    top->mti = mtip;
    uint64_t next_pc = exec_one_inst(commit);
    this->pc = next_pc;
    this->last_inst = commit.inst;
    this->last_pc = commit.pc;
	IFDEF(CONFIG_DIFFTEST, difftest_step(prev_pc));
    IFDEF(CONFIG_ITRACE, itrace->add(commit.pc, commit.inst));
}

void Core::take_trap(Trap trap_cause, word_t trap_pc) {
    if (fatal_trap(trap_cause)) {
        throw RVException { trap_cause, trap_pc, gpr[10] };
    }
#ifdef CONFIG_DIFFTEST
    if (is_intr(trap_cause)) {
        intr_event_queue.push_back((word_t)trap_cause);
    }
#endif
}

void Core::open_trace(const char* log_path) {
	if (log_path) { std::filesystem::create_directory(log_path); }
    const char* path = (log_path == nullptr) ? "" : log_path;
#ifdef CONFIG_ITRACE
    itrace = new ITrace((std::filesystem::path {path} / "itrace.log").c_str());
#endif
#ifdef CONFIG_VTRACE
    vtrace = new VTrace((std::filesystem::path {path} / "vtrace.vcd").c_str(), top, ctx);
#endif
}

void Core::dump_regs() {
    printf(ANSI_FMT("============================= <Register> =============================\n", ANSI_FG_MAGENTA));
    gpr.dump();
    csr.dump();
    printf("Program Counter Register: \n");
    printf("\t%-3s: " FMT_WORD "\t\n", "pc", this->pc);
    printf(ANSI_FMT("============================= <Register> =============================\n\n", ANSI_FG_MAGENTA));
}

void Core::connect_wire(axi4_ptr<32,64,4> &master_ptr, Vtop* top) {
    // aw   
    master_ptr.awaddr     = &(top->axi_awaddr);
    master_ptr.awburst    = &(top->axi_awburst);
    master_ptr.awid       = &(top->axi_awid);
    master_ptr.awlen      = &(top->axi_awlen);
    master_ptr.awready    = &(top->axi_awready);
    master_ptr.awsize     = &(top->axi_awsize);
    master_ptr.awvalid    = &(top->axi_awvalid);
    // w
    master_ptr.wdata      = &(top->axi_wdata);
    master_ptr.wlast      = &(top->axi_wlast);
    master_ptr.wready     = &(top->axi_wready);
    master_ptr.wstrb      = &(top->axi_wstrb);
    master_ptr.wvalid     = &(top->axi_wvalid);
    // b
    master_ptr.bid        = &(top->axi_bid);
    master_ptr.bready     = &(top->axi_bready);
    master_ptr.bresp      = &(top->axi_bresp);
    master_ptr.bvalid     = &(top->axi_bvalid);
    // ar
    master_ptr.araddr     = &(top->axi_araddr);
    master_ptr.arburst    = &(top->axi_arburst);
    master_ptr.arid       = &(top->axi_arid);
    master_ptr.arlen      = &(top->axi_arlen);
    master_ptr.arready    = &(top->axi_arready);
    master_ptr.arsize     = &(top->axi_arsize);
    master_ptr.arvalid    = &(top->axi_arvalid);
    // r
    master_ptr.rdata      = &(top->axi_rdata);
    master_ptr.rid        = &(top->axi_rid);
    master_ptr.rlast      = &(top->axi_rlast);
    master_ptr.rready     = &(top->axi_rready);
    master_ptr.rresp      = &(top->axi_rresp);
    master_ptr.rvalid     = &(top->axi_rvalid);

    // check
    master_ptr.check();
}

void Core::set_xbar(axi4_xbar<32,64,4>* xbar) {
    this->xbar = xbar;
}
